/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include "SnapshotBackup.h"
#include "log/Log.h"
#include "common/constant/ErrorCodes.h"
using namespace Module;
using namespace std;

namespace {
    const auto MODULE = "SnapshotBackup";
    const string SNAPSHOT_TAG = "";
    constexpr int NUMBER100 = 100;
    struct AntiCopyExtendInfo {
        std::string snapshotId;
        std::string snapshotName;
        std::string filesystemName;
        std::string filesystemId;

        BEGIN_SERIAL_MEMEBER
        SERIAL_MEMBER_TO_SPECIFIED_NAME(snapshotId, snapshotId)
        SERIAL_MEMBER_TO_SPECIFIED_NAME(snapshotName, snapshotName)
        SERIAL_MEMBER_TO_SPECIFIED_NAME(filesystemName, filesystemName)
        SERIAL_MEMBER_TO_SPECIFIED_NAME(filesystemId, filesystemId)
        END_SERIAL_MEMEBER
    };
    struct AdvanceParam {
        std::string snapTag;
        BEGIN_SERIAL_MEMEBER
        SERIAL_MEMBER_TO_SPECIFIED_NAME(snapTag, snapTag)
        END_SERIAL_MEMEBER
    };
}
using namespace std;


SnapshotBackup::~SnapshotBackup() {}


int SnapshotBackup::PrerequisiteJob()
{
    HCP_Log(INFO, MODULE) << "enter snapshot backup PrerequisiteJob()" << HCPENDLOG;
    m_jobInfoPtr = dynamic_pointer_cast<AppProtect::BackupJob>(m_jobCommonInfo->GetJobInfo());
    int ret = PrerequisiteJobInner();
    if (ret == Module::FAILED) {
        ReportJobDetails("plugin_execute_prerequisit_task_fail_label",
            JobLogLevel::type::TASK_LOG_ERROR, SubJobStatus::FAILED, m_jobProgress);
        HCP_Log(ERR, MODULE) << "Backup PrerequisiteJob failed jobId: " << m_jobInfoPtr->jobId << HCPENDLOG;
    }
    SetJobToFinish();
    return ret;
}

int SnapshotBackup::PrerequisiteJobInner()
{
    ReportJobDetails(EMPTY_LABEL,
        JobLogLevel::type::TASK_LOG_INFO, SubJobStatus::COMPLETED, NUMBER100);
    return Module::SUCCESS;
}

int SnapshotBackup::GenerateSubJob()
{
    m_jobInfoPtr = dynamic_pointer_cast<AppProtect::BackupJob>(m_jobCommonInfo->GetJobInfo());
    m_jobId = m_jobInfoPtr->jobId;
    int ret = GenerateSubJobInner();
    if (ret == Module::FAILED) {
        ReportJobDetails("plugin_generate_subjob_fail_label",
            JobLogLevel::type::TASK_LOG_ERROR, SubJobStatus::FAILED, m_jobProgress);
        HCP_Log(ERR, MODULE) << "Backup GenerateSubJob failed, jobId is: " << m_jobInfoPtr->jobId << HCPENDLOG;
    }
    SetJobToFinish();
    return ret;
}

int SnapshotBackup::GenerateSubJobInner()
{
    HCP_Log(INFO, MODULE) << "Generate sub task, task id is " << m_jobId << HCPENDLOG;
    SubJob subJob;
    subJob.__set_jobId(m_jobId);
    subJob.__set_jobType(SubJobType::BUSINESS_SUB_JOB);
    subJob.__set_jobName("Host_Fileset");
    subJob.__set_policy(ExecutePolicy::ANY_NODE); // LOCAL_NODE
    std::vector<SubJob> vec;
    vec.push_back(subJob);
    ActionResult result;

    JobService::AddNewJob(result, vec);
    if (result.code != Module::SUCCESS) {
        HCP_Log(INFO, MODULE) << "Failed to generate sub job, main task id is " << m_jobId << HCPENDLOG;
        return Module::FAILED;
    }

    ReportJobDetails(EMPTY_LABEL,
        JobLogLevel::type::TASK_LOG_INFO, SubJobStatus::COMPLETED, NUMBER100);
    HCP_Log(INFO, MODULE) << "Success to generate sub job, main task id is " << m_jobId << HCPENDLOG;
    return Module::SUCCESS;
}

int SnapshotBackup::ExecuteSubJob()
{
    m_jobInfoPtr = dynamic_pointer_cast<AppProtect::BackupJob>(m_jobCommonInfo->GetJobInfo());
    m_subjobId = m_subJobInfo->subJobId;
    m_jobId = m_subJobInfo->jobId;
    int ret = ExecuteSubJobInner();
    if (ret == Module::FAILED) {
        ReportJobDetails("plugin_backup_subjob_fail_label",
            JobLogLevel::type::TASK_LOG_ERROR, SubJobStatus::FAILED, m_jobProgress);
        HCP_Log(ERR, MODULE) << "Snapshot backup failed, jobId is: " << m_jobId <<
            " subjobid is:" << m_subjobId << HCPENDLOG;
    }
    SetJobToFinish();
    return ret;
}

int SnapshotBackup::ExecuteSubJobInner()
{
    HCP_Log(INFO, MODULE) << "Enter execute subJob, jobid is: " << m_jobId
                            << "  subjobid is: " << m_subjobId << HCPENDLOG;
    ActionResult result;
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    std::vector<LogDetail> logDetailList;

    if (InitJobInfo() != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Init job info fail, main task id is " << m_parentJobId << HCPENDLOG;
        ReportJobDetails("nas_plugin_init_env_fail_label",
            JobLogLevel::type::TASK_LOG_ERROR, SubJobStatus::FAILED, m_jobProgress);
        return Module::FAILED;
    }

    if (InitDeviceInfo() != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Init device info fail, main task id is " << m_parentJobId << HCPENDLOG;
        ReportJobDetails("nas_plugin_init_env_fail_label",
            JobLogLevel::type::TASK_LOG_ERROR, SubJobStatus::FAILED, m_jobProgress);
        return Module::FAILED;
    }

    if (IsAbortJob()) {
        HCP_Log(ERR, MODULE) << "the task has been aborted,the job id is: " << m_parentJobId << HCPENDLOG;
        ReportJobDetails(EMPTY_LABEL,
            JobLogLevel::type::TASK_LOG_WARNING, SubJobStatus::ABORTED, m_jobProgress);
        return Module::FAILED;
    }

    if (CreateSnapshot() != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Create snapshot failed" << HCPENDLOG;
        return Module::FAILED;
    }

    ReportJobDetails(EMPTY_LABEL,
        JobLogLevel::type::TASK_LOG_INFO, SubJobStatus::COMPLETED, NUMBER100);
    return Module::SUCCESS;
}

int SnapshotBackup::InitJobInfo()
{
    m_deviceInfo.username = m_jobInfoPtr->protectEnv.auth.authkey;
    m_deviceInfo.password = m_jobInfoPtr->protectEnv.auth.authPwd;
    m_deviceInfo.port = std::to_string(m_jobInfoPtr->protectEnv.port);
    m_deviceInfo.fileSystemName = m_jobInfoPtr->protectObject.name;
    m_deviceInfo.ipList = m_jobInfoPtr->protectEnv.endpoint;

    ProtectObjectExtendInfo extendInfo {};
    if (!JsonHelper::JsonStringToStruct(m_jobInfoPtr->protectObject.extendInfo, extendInfo)) {
        HCP_Log(ERR, MODULE) << "ProtectObjectExtendInfo is invaild." << HCPENDLOG;
        return Module::FAILED;
    }
    m_deviceInfo.fileSystemId = extendInfo.fileSystemId;
    m_vstoreId = extendInfo.tenantId;

    HCP_Log(INFO, MODULE) << "m_deviceInfo.username: " << m_deviceInfo.username << HCPENDLOG;
    HCP_Log(INFO, MODULE) << "m_deviceInfo.port: " << m_deviceInfo.port << HCPENDLOG;
    HCP_Log(INFO, MODULE) << "m_deviceInfo.fileSystemName: " << m_deviceInfo.fileSystemName << HCPENDLOG;
    HCP_Log(INFO, MODULE) << "m_deviceInfo.ipList: " << m_deviceInfo.ipList << HCPENDLOG;
    HCP_Log(INFO, MODULE) << "m_deviceInfo.fileSystemId: " << m_deviceInfo.fileSystemId << HCPENDLOG;

    m_nasHost.vendorSubType = m_jobInfoPtr->protectEnv.subType;

    return Module::SUCCESS;
}

int SnapshotBackup::CreateSnapshot()
{
    std::string snapshotName = FormatSnapshotName(m_jobInfoPtr->copy.id);
    if (snapshotName.empty()) {
        HCP_Log(ERR, MODULE) << "Snapshot name is empty" << HCPENDLOG;
        ReportJobDetails("nas_plugin_snapshot_create_fail_label",
            JobLogLevel::type::TASK_LOG_WARNING, SubJobStatus::FAILED, m_jobProgress);
        return Module::FAILED;
    }
    HCP_Log(INFO, MODULE) << "Get snapshot name success,snapshotName is: " << snapshotName << HCPENDLOG;

    int ret = Module::FAILED;
    if (GetVendorType(m_nasHost.vendorSubType) == Module::STORAGE_ENUM::DORADO) {
        ret = CreateSnapshotOnDorado(snapshotName);
    }
    if (GetVendorType(m_nasHost.vendorSubType) == Module::STORAGE_ENUM::FUSIONSTORAGE) {
        ret = CreateSnapshotOnFusionStorage(snapshotName);
    }
    ActionResult result;
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    std::vector<LogDetail> logDetailList;
    if (ret != Module::SUCCESS) {
        AddLogDetail(logDetail, "nas_plugin_snapshot_create_fail_label", JobLogLevel::TASK_LOG_ERROR, snapshotName);
        AddErrCode(logDetail, m_localDeviceController->GetErrorCode());
        if (m_localDeviceController->GetErrorCode() == HomoErrorCode::SNAPSHOT_NUM_LIMIT_ERROR_CODE) {
            string des = m_localDeviceController->GetExtendInfo();
            int start = des.find_first_of('(');
            int end = des.find_first_of(')');
            string limit = des.substr(start + 1, end - start - 1);
            HCP_Log(INFO, MODULE) << "des is: " << des << HCPENDLOG;
            AddErrCode(logDetail, m_localDeviceController->GetErrorCode(), limit);
        }
        REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, m_curProcess, 0, SubJobStatus::FAILED);
        return ret;
    }

    HCP_Log(INFO, MODULE) << "Create snapshot successfully, snapshot name is: " << snapshotName << HCPENDLOG;
    AddLogDetail(logDetail, "nas_plugin_snapshot_create_label", JobLogLevel::TASK_LOG_INFO, snapshotName);
    REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, m_curProcess, 0, SubJobStatus::COMPLETED);

    return Module::SUCCESS;
}

int SnapshotBackup::CreateSnapshotOnDorado(const std::string& snapshotName)
{
    HCP_Log(INFO, MODULE) << "Enter Create Snapshot On Dorado" << HCPENDLOG;

    AdvanceParam advanceParam {};
    if (!JsonHelper::JsonStringToStruct(m_jobInfoPtr->extendInfo, advanceParam)) {
        HCP_Log(WARN, MODULE) << "advanceParams is invaild." << HCPENDLOG;
    }
    HCP_Log(INFO, MODULE) << "Get snapTag name success,snapTag is: " << advanceParam.snapTag << HCPENDLOG;
    std::string snapTag = advanceParam.snapTag.empty() ? SNAPSHOT_TAG : advanceParam.snapTag;
    std::string snapshotId;

    auto localDeviceControl = dynamic_pointer_cast<Module::DoradoNas>(m_localDeviceController);
    if (localDeviceControl->CreateSnapshotWithSnapTagAndVstoreId(snapshotName, snapTag, snapshotId, m_vstoreId) !=
        Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Create snapshot failed, Snapshot name is: " << snapshotName << HCPENDLOG;
        return Module::FAILED;
    }

    return Module::SUCCESS;
}

int SnapshotBackup::CreateSnapshotOnFusionStorage(const std::string& snapshotName)
{
    HCP_Log(INFO, MODULE) << "Enter Create Snapshot On FusionStorage" << HCPENDLOG;

    auto localDeviceControl = dynamic_pointer_cast<Module::FusionStorageNas>(m_localDeviceController);
    int errorCode = 0;
    if (localDeviceControl->CreateSnapshot(snapshotName, errorCode) == nullptr) {
        HCP_Log(ERR, MODULE) << "Create snapshot failed,"
            << " Snapshot name is: " << snapshotName
            << " Error code is: " << errorCode
            << HCPENDLOG;
        return Module::FAILED;
    }

    return Module::SUCCESS;
}

int SnapshotBackup::PostJob()
{
    m_jobInfoPtr = dynamic_pointer_cast<AppProtect::BackupJob>(m_jobCommonInfo->GetJobInfo());
    m_jobId = m_jobInfoPtr->jobId;
    m_subjobId = m_subJobInfo->subJobId;
    int ret = PostJobInner();
    if (ret == Module::FAILED) {
        ReportJobDetails(EMPTY_LABEL,
            JobLogLevel::type::TASK_LOG_ERROR, SubJobStatus::FAILED, m_jobProgress);
        HCP_Log(ERR, MODULE) << "Exit PostJobInner jobId: " << m_jobInfoPtr->jobId << HCPENDLOG;
        return Module::FAILED;
    }
    ReportJobDetails(EMPTY_LABEL,
        JobLogLevel::type::TASK_LOG_INFO, SubJobStatus::COMPLETED, NUMBER100);
    SetJobToFinish();
    return ret;
}

int SnapshotBackup::PostJobInner()
{
    HCP_Log(INFO, MODULE) << "Start to execute post job, main task id is " << m_parentJobId
        << ", sub task id is" << m_subjobId << HCPENDLOG;
    ActionResult result;
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    std::vector<LogDetail> logDetailList;

    if (InitJobInfo() != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Init job info fail, main task id is " << m_parentJobId << HCPENDLOG;
        ReportJobDetails("nas_plugin_snapshot_create_fail_label",
            JobLogLevel::type::TASK_LOG_WARNING, SubJobStatus::RUNNING, m_jobProgress);
        return Module::FAILED;
    }

    if (InitDeviceInfo() != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "Init device info fail, main task id is " << m_parentJobId << HCPENDLOG;
        ReportJobDetails("nas_plugin_snapshot_create_fail_label",
            JobLogLevel::type::TASK_LOG_WARNING, SubJobStatus::RUNNING, m_jobProgress);
        return Module::FAILED;
    }

    // 任务流程成功
    if (m_backupJobResult == AppProtect::JobResult::type::SUCCESS) {
        if (ReportCopyInfo() == Module::SUCCESS) {
            HCP_Log(INFO, MODULE) << "Report copy info success" << HCPENDLOG;
            return Module::SUCCESS;
        }
        HCP_Log(ERR, MODULE) << "Report copy info failed" << HCPENDLOG;
        return Module::FAILED;
    }

    // 任务流程失败 删除本地快照
    HCP_Log(INFO, MODULE) << "snapshot backup task failed, start to delete created snapshot" << HCPENDLOG;
    if (DeleteBackupSnapshot() != Module::SUCCESS) {
        AddLogDetail(logDetail, "nas_plugin_snapshot_delete_fail_label", JobLogLevel::TASK_LOG_WARNING);
        AddErrCode(logDetail, m_localDeviceController->GetErrorCode());
        REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::RUNNING);
        return Module::FAILED;
    }
    REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, NUMBER100, 0, SubJobStatus::COMPLETED);
    HCP_Log(INFO, MODULE) << "Finish to execute post job, main task id is " << m_parentJobId << HCPENDLOG;
    return Module::SUCCESS;
}

int SnapshotBackup::ReportCopyInfo()
{
    ActionResult result;
    Copy image;
    std::string snapshotName = FormatSnapshotName(m_jobInfoPtr->copy.id);
    image.__set_name(snapshotName); // 任务ID作为快照名称

    string antiSnapshotId;
    if (m_localDeviceController->QuerySnapshot(snapshotName, antiSnapshotId) != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "failed to query snapShot,snapshot name is: " << snapshotName << HCPENDLOG;
        return Module::FAILED;
    }
    HCP_Log(INFO, MODULE) << "SnapshotId is: " << antiSnapshotId << HCPENDLOG;
    AntiCopyExtendInfo copyExtendInfo;
    copyExtendInfo.snapshotId = antiSnapshotId;
    copyExtendInfo.snapshotName = snapshotName;
    copyExtendInfo.filesystemName = m_deviceInfo.fileSystemName;
    copyExtendInfo.filesystemId = m_deviceInfo.fileSystemId;
    
    std::string extendInfo {};
    if (!JsonHelper::StructToJsonString(copyExtendInfo, extendInfo)) {
        HCP_Log(ERR, MODULE) << "copyExtendInfo transfer failed." << HCPENDLOG;
        return Module::FAILED;
    }
    image.__set_extendInfo(extendInfo);
    HCP_Log(INFO, MODULE) << "Report copy info" << WIPE_SENSITIVE(image.name) << HCPENDLOG;
    JobService::ReportCopyAdditionalInfo(result, m_parentJobId, image);
    return Module::SUCCESS;
}

int SnapshotBackup::DeleteBackupSnapshot()
{
    std::string snapshotName = FormatSnapshotName(m_jobInfoPtr->copy.id);
    HCP_Log(INFO, MODULE) << "Task is not completed. Delete local snapshot and copy" << HCPENDLOG;
    // 删除本端快照
    ActionResult result;
    SubJobDetails subJobDetails;
    LogDetail logDetail{};
    std::vector<LogDetail> logDetailList;
    AddLogDetail(logDetail, "nas_plugin_snapshot_delete_basic_label", JobLogLevel::TASK_LOG_INFO, snapshotName);
    REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::RUNNING);

    if (snapshotName.empty()) {
        AddLogDetail(logDetail, "nas_plugin_snapshot_delete_fail_label", JobLogLevel::TASK_LOG_WARNING);
        AddErrCode(logDetail, m_localDeviceController->GetErrorCode());
        REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::RUNNING);
        HCP_Log(WARN, MODULE) << "snapshotName is empty" << HCPENDLOG;
        return Module::FAILED;
    }
    std::string snapshotId;
    if (m_localDeviceController->QuerySnapshot(snapshotName, snapshotId) != Module::SUCCESS) {
        HCP_Log(ERR, MODULE) << "failed to query snapShot,snapshot name is: " << snapshotName << HCPENDLOG;
        return Module::SUCCESS;
    }
    if (m_localDeviceController->DeleteSnapshot(snapshotName) != Module::SUCCESS) {
        AddLogDetail(logDetail, "nas_plugin_snapshot_delete_fail_label", JobLogLevel::TASK_LOG_WARNING);
        AddErrCode(logDetail, m_localDeviceController->GetErrorCode());
        REPORT_LOG2AGENT(subJobDetails, result, logDetailList, logDetail, 0, 0, SubJobStatus::RUNNING);
        HCP_Log(ERR, MODULE) << "Delete snapshot in localstorage fail： " << snapshotName << HCPENDLOG;
        return Module::FAILED;
    }
    HCP_Log(INFO, MODULE) << "Delete snapshot success. the snapshotname is : " << snapshotName << HCPENDLOG;
    return Module::SUCCESS;
}